#include "BufferedStream.h"
#include <cstring>

using namespace boost;

namespace lhcutils
{

BufferedStream::BufferedStream(boost::shared_ptr<IStream> stream, int32_t bufferSize)
	: m_stream(stream), m_buffer(bufferSize), m_bytesInBuffer(0),
	m_bufferPos(0), m_reading(true)
{
	;
}

BufferedStream::~BufferedStream()
{
	;
}

void BufferedStream::Close()
{
	m_stream->Close();
}

size_t BufferedStream::GetBufferSize()
{
	return m_buffer.size();
}

bool BufferedStream::CanRead()
{
	return m_stream->CanRead();
}

bool BufferedStream::CanSeek()
{
	return m_stream->CanSeek();
}

bool BufferedStream::CanWrite()
{
	return m_stream->CanWrite();
}

int64_t BufferedStream::GetPosition()
{
	return m_stream->GetPosition() + GetOffsetFromUnderlyingStreamPosition();
}

int64_t BufferedStream::GetOffsetFromUnderlyingStreamPosition()
{
	if(m_reading)
	{
		return static_cast<boost::int64_t>(m_bufferPos) - static_cast<boost::int64_t>(m_bytesInBuffer);
	}
	else
	{
		return m_bufferPos;
	}
}

void BufferedStream::SetPosition(int64_t newPosition, SeekOrigin::Value origin)
{
	// If there's anything in the write buffer, write it to the underlying stream
	if(!m_reading && m_bytesInBuffer > 0)
	{
		m_stream->Write(&(m_buffer[0]), m_bytesInBuffer);
		m_bytesInBuffer = 0;
		m_bufferPos = 0;
	}

	if(m_bufferPos > 0 && origin == SeekOrigin::Current)
	{
		// convert actual to underlying
		newPosition = newPosition + GetOffsetFromUnderlyingStreamPosition();
	}

	m_stream->SetPosition(newPosition, origin);
	m_bytesInBuffer = 0; // buffer is no longer valid
	m_bufferPos = 0;
}

void BufferedStream::Flush()
{
	// If there's anything in the write buffer, write it to the underlying stream
	if(!m_reading && m_bytesInBuffer > 0)
	{
		m_stream->Write(&(m_buffer[0]), m_bytesInBuffer);
		m_bytesInBuffer = 0;
		m_bufferPos = 0;
	}

	// Flush the underlying stream
	m_stream->Flush();
}

size_t BufferedStream::Read(void* buffer, size_t count)
{
	// Switch to read mode
	if(!m_reading && m_bytesInBuffer > 0)
	{
		m_stream->Write(&(m_buffer[0]), m_bytesInBuffer);
		m_bytesInBuffer = 0;
		m_bufferPos = 0;
	}

	m_reading = true;

	if(m_bytesInBuffer == 0)
	{
		if(count >= GetBufferSize())
		{
			// Just satisfy the request from the stream directly.
			return m_stream->Read(buffer, count);
		}
		else
		{
			// Fill the read buffer
			m_bytesInBuffer = m_stream->Read(&(m_buffer[0]), GetBufferSize());
			m_bufferPos = 0;

			// Satisfy the request from the buffer
			size_t numBytesToReturn = count;
			if(m_bytesInBuffer < count)
			{
				numBytesToReturn = m_bytesInBuffer;
			}

			std::memcpy(buffer, &(m_buffer[m_bufferPos]), numBytesToReturn);

			m_bufferPos += numBytesToReturn;

			return numBytesToReturn;
		}
	}
	else
	{
		// If we've returned all bytes in the buffer, refill the buffer
		if(m_bufferPos >= m_bytesInBuffer)
		{
			m_bytesInBuffer = m_stream->Read(&(m_buffer[0]), GetBufferSize());
			m_bufferPos = 0;
		}

		size_t numBytesAvailable = m_bytesInBuffer - m_bufferPos;

		size_t numBytesToReturn = count;
		if(numBytesAvailable < count)
		{
			numBytesToReturn = numBytesAvailable;
		}

		std::memcpy(buffer, &(m_buffer[m_bufferPos]), numBytesToReturn);

		m_bufferPos += numBytesToReturn;

		return numBytesToReturn;
	}
}

void BufferedStream::Write(const void* buffer, size_t count)
{
	// If in read mode, set to write mode and seek the stream back to where the caller of the stream expects the stream to be at.
	if(m_reading)
	{
		int64_t numBytesToSeekBackward = m_bytesInBuffer - m_bufferPos;

		if(numBytesToSeekBackward != 0)
		{
			m_stream->Seek(-numBytesToSeekBackward, SeekOrigin::Current);
		}

		m_bytesInBuffer = 0;

		m_bufferPos = 0;

		m_reading = false;
	}

	// Append it to the write buffer if there is space.
	// If there is not space, write out the contents of the write buffer.
	// If there is enough space in the write buffer now, append it to the write buffer.
	// Otherwise, write it out directly.

	size_t bytesAvailable = GetBufferSize() - m_bytesInBuffer;
	if(bytesAvailable >= count)
	{
		std::memcpy(&(m_buffer[m_bufferPos]), buffer, count);
		m_bytesInBuffer += count;
		m_bufferPos += count;
	}
	else
	{
		m_stream->Write(&(m_buffer[0]), m_bytesInBuffer);
		m_bytesInBuffer = 0;
		m_bufferPos = 0;

		bytesAvailable = GetBufferSize();
		if(bytesAvailable >= count)
		{
			std::memcpy(&(m_buffer[m_bufferPos]), buffer, count);
			m_bytesInBuffer += count;
			m_bufferPos += count;
		}
		else
		{
			m_stream->Write(buffer, count);
		}
	}
}

}

/*
 Copyright 2011 Greg Najda

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
